package main

import (
	"encoding/json"
	"flag"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 512
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

type UserState int

const (
	UserState_Gaming UserState = iota
	UserState_Observer
)

type Client struct {
	conn      *websocket.Conn
	username  string
	hub       *Hub
	send      chan []byte
	response  chan *ClientResponse
	userstate UserState
	gameid    int
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type Hub struct {
	clients    map[*Client]bool
	broadcast  chan []byte
	register   chan *Client
	unregister chan *Client
	cmd        chan *Command
}

func newHub() *Hub {
	return &Hub{
		broadcast:  make(chan []byte),
		clients:    make(map[*Client]bool),
		register:   make(chan *Client),
		unregister: make(chan *Client),
		cmd:        make(chan *Command),
	}
}

const CMD_TYPE_ERR string = "ERR"

type ClientResponse struct {
	Type string
	Arg  interface{}
	Data string
}

func (h *Hub) getClientNames() []string {
	onlineClients := make([]string, len(h.clients))
	index := 0
	for k, _ := range h.clients {
		onlineClients[index] = k.username
		index++
	}
	return onlineClients
}

func (h *Hub) processCmd(cmd *Command) {
	log.Printf("recv cmd: %#v", cmd)
	if cmd.Type == CMD_TYPE_ERR {
		response := ClientResponse{Type: cmd.Type, Arg: cmd.Arg, Data: cmd.Data}
		cmd.OriginClient.response <- &response
	}
	if cmd.Type == "BATTLE" {
		battleResponse := ClientResponse{Type: cmd.Type, Arg: cmd.OriginClient.username, Data: "用户：[" + cmd.OriginClient.username + "]向您发出Battle"}
		go func() {
			var client *Client = nil
			for c := range h.clients {
				if c.username == cmd.Arg {
					client = c
					break
				}
			}
			if client == nil {
				errResponse := &ClientResponse{Type: "ERROR", Arg: cmd.Arg, Data: "您的命令" + cmd.Type + "异常"}
				cmd.OriginClient.response <- errResponse
			} else {
				client.response <- &battleResponse
			}
		}()
	}
	if cmd.Type == "SINGLE-CHAT" {
		chatResponse := ClientResponse{Type: cmd.Type, Arg: cmd.OriginClient.username, Data: "[" + cmd.OriginClient.username + "]: " + cmd.Data}
		go func() {
			var client *Client = nil
			for c := range h.clients {
				if c.username == cmd.Arg {
					client = c
					break
				}
			}
			if client == nil {
				errResponse := &ClientResponse{Type: "ERROR", Arg: cmd.Arg, Data: "您的命令" + cmd.Type + "异常"}
				cmd.OriginClient.response <- errResponse
			} else {
				client.response <- &chatResponse
			}
		}()
	}
	if cmd.Type == "BROADCAST" {
		response := ClientResponse{Type: cmd.Type, Arg: cmd.Arg, Data: cmd.Data}
		for c := range h.clients {
			log.Println("向client.response chan:", c.username, "发送广播", response)
			select {
			case c.response <- &response:
			default:
				log.Println("message close")
				close(c.response)
				delete(h.clients, c)
			}
		}
	}
}

func (h *Hub) sendWellcome(c *Client) {
	loginCmd := &Command{Type: "BROADCAST", Arg: h.getClientNames(), Data: "欢迎用户:" + c.username}
	h.cmd <- loginCmd
}

func (h *Hub) run() {
	for {
		select {
		case client := <-h.register:
			h.clients[client] = true
			go h.sendWellcome(client)
		case client := <-h.unregister:
			if _, ok := h.clients[client]; ok {
				delete(h.clients, client)
				close(client.response)
				log.Println("unregister delete map data, close chan")
			}
			logoutCmd := &Command{Type: "BROADCAST", Data: "用户:" + client.username + "退出登录", Arg: client.hub.getClientNames()}
			go func() {
				h.cmd <- logoutCmd
			}()
		case cmd := <-h.cmd:
			h.processCmd(cmd)
			// log.Printf("recv cmd: %#v", cmd)
			// response := ClientResponse{Type: cmd.Type, Arg: cmd.OriginClient.username, Data: cmd.Data}
			// for c := range h.clients {
			// 	log.Println("向client.response chan:", c.username, "发送广播", response)
			// 	select {
			// 	case c.response <- &response:
			// 	default:
			// 		log.Println("message close")
			// 		close(c.response)
			// 		delete(h.clients, c)
			// 		delete(h.clientsUsername, c.username)
			// 	}
			// }
		case message := <-h.broadcast:
			for client := range h.clients {
				select {
				case client.send <- message:
				default:
					log.Println("message close")
					close(client.send)
					delete(h.clients, client)
				}
			}
		}
	}
}

func (c *Client) readPump() {
	defer func() {
		log.Println("send unregister client chan data, close conn")
		c.hub.unregister <- c
		c.conn.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	// c.conn.SetReadDeadline(time.Now().Add(pongWait))
	// c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		var cmd Command
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("recv data error: %v", err)
			}
			if websocket.IsCloseError(err, websocket.CloseGoingAway) {
				log.Println("going away error")
			}
			log.Println("read message err:", err)

			break

		}

		_ = json.Unmarshal(message, &cmd)
		log.Printf("发送正常命令:%#v", cmd)
		cmd.OriginClient = c
		c.hub.cmd <- &cmd // 发送命令到

	}

}

type Command struct {
	OriginClient *Client
	Type         string
	Arg          interface{}
	Data         string
}

func (c *Client) writePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()
	for {
		select {
		case response := <-c.response:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			// if !ok {
			// 	// The hub closed the channel.
			// 	c.conn.WriteMessage(websocket.CloseMessage, []byte{})
			// 	return
			// }
			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			r, _ := json.Marshal(response)
			log.Println("revc chan data:", c.username, string(r))
			w.Write(r)
			w.Close()
		case message, ok := <-c.send:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				// The hub closed the channel.
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			w.Write(message)

			// Add queued chat messages to the current websocket message.
			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				w.Write(<-c.send)
			}

			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// serveWs handles websocket requests from the peer.
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
	log.Println("请求地址:", r.URL.Path)
	query := r.URL.Query()
	username := query.Get("username")

	//

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	if len(username) == 0 {
		// http.Error(w, "invalid username", http.StatusBadRequest)
		w, _ := conn.NextWriter(websocket.TextMessage)
		response := ClientResponse{Type: "ERR", Data: "用户名不能为空"}
		j, _ := json.Marshal(response)
		w.Write(j)
		return
	}

	logined := false
	for c := range hub.clients {
		if c.username == username {
			logined = true
		}
	}

	if logined {
		w, _ := conn.NextWriter(websocket.TextMessage)
		response := ClientResponse{Type: "ERR", Data: "不能重复登录"}
		j, _ := json.Marshal(response)
		log.Println("不能重复登录json:", string(j))
		w.Write(j)
		w.Close()
		conn.Close()
		return
	}

	client := &Client{conn: conn, hub: hub, send: make(chan []byte, 256), username: username, response: make(chan *ClientResponse)}
	hub.register <- client

	log.Printf("用户连接成功 :%s", client.username)

	go client.writePump()
	go client.readPump()
}

func serveHome(w http.ResponseWriter, r *http.Request) {
	log.Println(r.URL)
	http.ServeFile(w, r, "home.html")
}

func main() {
	// // echo := Echo{}
	// echo.init()
	// echo.startServe()
	addr := flag.String("addr", ":8080", "server address")
	log.Println("addr:", *addr)

	hub := newHub()
	go hub.run()

	http.HandleFunc("/", serveHome)
	http.HandleFunc("/ws", func(rw http.ResponseWriter, r *http.Request) {
		serveWs(hub, rw, r)
	})
	http.HandleFunc("/online", func(rw http.ResponseWriter, r *http.Request) {
		rw.Header().Add("content-type", "application/json;charset=utf8")
		// clientMap := make(map[string]string)
		onlineClients := make([]OnlineClient, len(hub.clients))
		index := 0
		for k := range hub.clients {
			online := OnlineClient{Username: k.username, Ip: k.conn.LocalAddr().String()}
			onlineClients[index] = online
			index++
		}

		json, _ := json.Marshal(onlineClients)
		rw.Write([]byte(json))
	})
	err := http.ListenAndServe(*addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

type OnlineClient struct {
	Username string
	Ip       string
}
